package org.jeecg.common.util;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class CryptoUtil {

    private static final String TRANSFORMATION = "AES/GCM/NoPadding";
    private static final String ALGORITHM = "AES";
    private static final int TAG_LENGTH_BIT = 256;
    private static final int IV_LENGTH_BYTE = 16;
    private static final int KEY_LENGTH_BYTE = 32;

    /**
     * 生成一个随机的AES加密密钥。
     *
     * @return 以字符串形式返回生成的密钥。
     */
    public static String generateKey() {
        byte[] key = new byte[KEY_LENGTH_BYTE];
        new SecureRandom().nextBytes(key);
        return Base64.getEncoder().encodeToString(key);
    }

    /**
     * 生成一个随机的AES初始化向量，并以Base64编码的字符串形式返回。
     *
     * @return 以字符串形式返回生成的初始化向量。
     */
    public static String generateIV() {
        byte[] iv = new byte[IV_LENGTH_BYTE];
        new SecureRandom().nextBytes(iv);
        return Base64.getEncoder().encodeToString(iv);
    }

    /**
     * 使用给定的密钥和初始化向量加密给定的明文。
     *
     * @param plaintext 要加密的明文。
     * @param key       加密使用的密钥。
     * @param iv        加密使用的初始化向量。
     * @return 以Base64编码的字符串形式返回加密后的密文。
     * @throws Exception 如果在加密过程中发生错误。
     */
    public static String encrypt(String plaintext, String key, String iv) throws Exception {
        if (plaintext == null || key == null || iv == null) {
            throw new IllegalArgumentException("plaintext, key, iv cannot be null.");
        }
        if (key.length() != KEY_LENGTH_BYTE || iv.length() != IV_LENGTH_BYTE) {
            throw new IllegalArgumentException("key or iv length is invalid.");
        }
        byte[] plaintextBytes = plaintext.getBytes();
        byte[] ivBytes = Base64.getDecoder().decode(iv);
        byte[] keyBytes = Base64.getDecoder().decode(key);
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, ivBytes);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, parameterSpec);
        byte[] encryptedData = cipher.doFinal(plaintextBytes);
        byte[] ivAndEncryptedData = new byte[ivBytes.length + encryptedData.length];
        System.arraycopy(ivBytes, 0, ivAndEncryptedData, 0, ivBytes.length);
        System.arraycopy(encryptedData, 0, ivAndEncryptedData, ivBytes.length, encryptedData.length);
        return Base64.getEncoder().encodeToString(ivAndEncryptedData);
    }

    /**
     * 使用给定的密钥和初始化向量解密给定的密文。
     *
     * @param ciphertext 要解密的密文。
     * @param key        解密使用的密钥。
     * @param iv         解密使用的初始化向量。
     * @return 解密后的明文。
     * @throws Exception 如果在解密过程中发生错误。
     */
    public static String decrypt(String ciphertext, String key, String iv) throws Exception {
        if (ciphertext == null || ciphertext.isEmpty()) {
            throw new IllegalArgumentException("Ciphertext cannot be null or empty.");
        }
        if (key == null || key.isEmpty()) {
            throw new IllegalArgumentException("Key cannot be null or empty.");
        }
        if (iv == null || iv.isEmpty()) {
            throw new IllegalArgumentException("IV cannot be null or empty.");
        }
        if (key.length() != KEY_LENGTH_BYTE || iv.length() != IV_LENGTH_BYTE) {
            throw new IllegalArgumentException("Key or IV length is not valid.");
        }
        byte[] ciphertextBytes = Base64.getDecoder().decode(ciphertext);
        byte[] ivBytes = Base64.getDecoder().decode(iv);
        byte[] encryptedData = new byte[ciphertextBytes.length - IV_LENGTH_BYTE];
        System.arraycopy(ciphertextBytes, 0, ivBytes, 0, IV_LENGTH_BYTE);
        System.arraycopy(ciphertextBytes, IV_LENGTH_BYTE, encryptedData, 0, encryptedData.length);
        byte[] keyBytes = Base64.getDecoder().decode(key);
        SecretKey secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, ivBytes);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
        byte[] decryptedData = cipher.doFinal(encryptedData);
        return new String(decryptedData);
    }

}

